using SharpGraphT.Graph.Specifics;
using SharpGraphT.Sharp.Sharp.Util;

namespace SharpGraphT.Graph;

[Serializable]
public abstract class AbstractBaseGraph<TV, TE> : AbstractGraph<TV, TE>,
    IGraph<TV, TE>, ICloneable where TE : class, new() {
    private static readonly long SerialVersionUid = -3582386521833998627L;

    private const string LoopsNotAllowed = "loops not allowed";

    private const string GraphSpecificsMustNotBeNull =
        "Graph specifics must not be null";

    private const string
        InvalidVertexSupplierDoesNotReturnUniqueVerticesOnEachCall =
            "Invalid vertex supplier (does not return unique vertices on each call).";

    private const string MixedGraphNotSupported = "Mixed graph not supported";

    private const string GraphSpecificsStrategyRequired =
        "Graph specifics strategy required";

    private const string TheGraphContainsNoVertexSupplier =
        "The graph contains no vertex supplier";

    private const string TheGraphContainsNoEdgeSupplier =
        "The graph contains no edge supplier";

    private Func<TV> _vertexSupplier;
    private Func<TE> _edgeSupplier;
    private IGraphType _type;

    private ISpecifics<TV, TE> _specifics;
    private IIntrusiveEdgesSpecifics<TV, TE> _intrusiveEdgesSpecifics;
    private IGraphSpecificsStrategy<TV, TE> _graphSpecificsStrategy;

    [NonSerialized] private IGraphIterables<TV, TE> _graphIterables;

    protected AbstractBaseGraph(Func<TV> vertexSupplier, Func<TE> edgeSupplier,
        IGraphType type) : this(vertexSupplier, edgeSupplier, type,
        new FastLookupGraphSpecificsStrategy<TV, TE>()) { }

    protected AbstractBaseGraph(Func<TV> vertexSupplier, Func<TE> edgeSupplier,
        IGraphType type,
        IGraphSpecificsStrategy<TV, TE> graphSpecificsStrategy) {
        _vertexSupplier = vertexSupplier;
        _edgeSupplier = edgeSupplier;
        _type = Objects.RequireNonNull(type);
        if (type.IsMixed) {
            throw new ArgumentException(MixedGraphNotSupported);
        }

        _graphSpecificsStrategy = Objects.RequireNonNull(graphSpecificsStrategy,
            GraphSpecificsStrategyRequired);
        _specifics = graphSpecificsStrategy.GetSpecificsFactory()(this, type);
        Objects.RequireNonNull(_specifics, GraphSpecificsMustNotBeNull);
        _intrusiveEdgesSpecifics = graphSpecificsStrategy
            .GetIntrusiveEdgesSpecificsFactory()(type);
        Objects.RequireNonNull(_intrusiveEdgesSpecifics,
            GraphSpecificsMustNotBeNull);
    }

    public override IReadOnlySet<TE> GetAllEdges(TV sourceVertex,
        TV targetVertex) =>
        _specifics.GetAllEdges(sourceVertex, targetVertex);

    public override Func<TE> EdgeSupplier => _edgeSupplier;

    public void SetEdgeSupplier(Func<TE> edgeSupplier) =>
        _edgeSupplier = edgeSupplier;

    public override Func<TV> VertexSupplier => _vertexSupplier;

    public void SetVertexSupplier(Func<TV> vertexSupplier) =>
        _vertexSupplier = vertexSupplier;

    public override TE GetEdge(TV sourceVertex, TV targetVertex) =>
        _specifics.GetEdge(sourceVertex, targetVertex);

    public override TE AddEdge(TV sourceVertex, TV targetVertex) {
        AssertVertexExist(sourceVertex);
        AssertVertexExist(targetVertex);

        if (!_type.IsAllowingSelfLoops && sourceVertex.Equals(targetVertex)) {
            throw new ArgumentException(LoopsNotAllowed);
        }

        if (_edgeSupplier == null) {
            throw new InvalidOperationException(TheGraphContainsNoEdgeSupplier);
        }

        if (!_type.IsAllowingMultipleEdges) {
            var e = _specifics.CreateEdgeToTouchingVerticesIfAbsent(
                sourceVertex, targetVertex, _edgeSupplier);
            if (e == null) {
                return null;
            }

            var edgeAdded = false;
            try {
                edgeAdded = _intrusiveEdgesSpecifics.Add(e,
                    sourceVertex, targetVertex);
            } finally {
                if (!edgeAdded) {
                    _specifics.RemoveEdgeFromTouchingVertices(sourceVertex,
                        targetVertex, e);
                }
            }

            if (edgeAdded) {
                return e;
            }
        } else {
            var e = _edgeSupplier();
            if (!_intrusiveEdgesSpecifics.Add(e, sourceVertex, targetVertex)) {
                return null;
            }

            _specifics.AddEdgeToTouchingVertices(sourceVertex, targetVertex, e);
            return e;
        }

        return null;
    }

    public override bool AddEdge(TV sourceVertex, TV targetVertex, TE e) {
        if (e == null) {
            throw new NullReferenceException();
        }

        AssertVertexExist(sourceVertex);
        AssertVertexExist(targetVertex);

        if (!_type.IsAllowingSelfLoops && sourceVertex.Equals(targetVertex)) {
            throw new ArgumentException(LoopsNotAllowed);
        }

        if (!_type.IsAllowingMultipleEdges) {
            if (!_specifics.AddEdgeToTouchingVerticesIfAbsent(sourceVertex,
                    targetVertex, e)) {
                return false;
            }

            var edgeAdded = false;
            try {
                edgeAdded =
                    _intrusiveEdgesSpecifics.Add(e, sourceVertex, targetVertex);
            } finally {
                if (!edgeAdded) {
                    _specifics.RemoveEdgeFromTouchingVertices(sourceVertex,
                        targetVertex, e);
                }
            }

            return edgeAdded;
        }

        if (!_intrusiveEdgesSpecifics.Add(e, sourceVertex, targetVertex)) {
            return false;
        }

        _specifics.AddEdgeToTouchingVertices(sourceVertex, targetVertex, e);
        return true;
    }

    public override TV AddVertex() {
        if (_vertexSupplier == null) {
            throw new InvalidOperationException(
                TheGraphContainsNoVertexSupplier);
        }

        var v = _vertexSupplier();
        Objects.RequireNonDefault(v,
            "Vertex of a default value should not be added");

        if (!_specifics.AddVertex(v)) {
            throw new ArgumentException(
                InvalidVertexSupplierDoesNotReturnUniqueVerticesOnEachCall);
        }

        return v;
    }

    public override bool AddVertex(TV v) {
        if (v == null) {
            throw new NullReferenceException();
        }

        Objects.RequireNonDefault(v,
            "Vertex of a default value should not be added");

        if (ContainsVertex(v)) {
            return false;
        }

        _specifics.AddVertex(v);
        return true;
    }

    public override TV GetEdgeSource(TE e) =>
        _intrusiveEdgesSpecifics.GetEdgeSource(e);

    public override TV GetEdgeTarget(TE e) =>
        _intrusiveEdgesSpecifics.GetEdgeTarget(e);

    public object Clone() {
        var newGraph = (AbstractBaseGraph<TV, TE>)MemberwiseClone();

        newGraph._vertexSupplier = _vertexSupplier;
        newGraph._edgeSupplier = _edgeSupplier;
        newGraph._type = _type;

        newGraph._graphSpecificsStrategy = _graphSpecificsStrategy;

        newGraph._specifics =
            newGraph._graphSpecificsStrategy.GetSpecificsFactory()(newGraph,
                newGraph._type);
        newGraph._intrusiveEdgesSpecifics = newGraph
            ._graphSpecificsStrategy
            .GetIntrusiveEdgesSpecificsFactory()(newGraph._type);

        newGraph._graphIterables = null;

        newGraph.AddGraph(this);

        return newGraph;
    }

    public override bool ContainsEdge(TE e) =>
        _intrusiveEdgesSpecifics.ContainsEdge(e);

    public override bool ContainsVertex(TV v) =>
        _specifics.VertexSet.Contains(v);

    public override int DegreeOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.DegreeOf(vertex);
    }

    public override IReadOnlySet<TE> EdgeSet =>
        _intrusiveEdgesSpecifics.EdgeSet;

    public override IReadOnlySet<TE> EdgesOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.EdgesOf(vertex);
    }

    public override int InDegreeOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.InDegreeOf(vertex);
    }

    public override IReadOnlySet<TE> IncomingEdgesOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.IncomingEdgesOf(vertex);
    }

    public override int OutDegreeOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.OutDegreeOf(vertex);
    }

    public override IReadOnlySet<TE> OutgoingEdgesOf(TV vertex) {
        AssertVertexExist(vertex);
        return _specifics.OutgoingEdgesOf(vertex);
    }

    public override TE RemoveEdge(TV sourceVertex, TV targetVertex) {
        var e = GetEdge(sourceVertex, targetVertex);

        if (e == null) {
            return e;
        }

        _specifics.RemoveEdgeFromTouchingVertices(sourceVertex, targetVertex,
            e);
        _intrusiveEdgesSpecifics.Remove(e);

        return e;
    }

    public override bool RemoveEdge(TE e) {
        if (!ContainsEdge(e)) {
            return false;
        }

        var sourceVertex = GetEdgeSource(e);
        var targetVertex = GetEdgeTarget(e);
        _specifics.RemoveEdgeFromTouchingVertices(sourceVertex, targetVertex,
            e);
        _intrusiveEdgesSpecifics.Remove(e);
        return true;
    }

    public override bool RemoveVertex(TV v) {
        if (!ContainsVertex(v)) {
            return false;
        }

        RemoveAllEdges(EdgesOf(v).ToList());
        _specifics.RemoveVertex(v);
        return true;
    }

    public override IReadOnlySet<TV> VertexSet => _specifics.VertexSet;

    public override double GetEdgeWeight(TE e) =>
        e == null
            ? throw new NullReferenceException()
            : _intrusiveEdgesSpecifics.GetEdgeWeight(e);

    public override void SetEdgeWeight(TE e, double weight) {
        if (e == null) {
            throw new NullReferenceException();
        }

        _intrusiveEdgesSpecifics.SetEdgeWeight(e, weight);
    }

    public override IGraphType Type => _type;

    IGraphIterables<TV, TE> IGraph<TV, TE>.Iterables =>
        _graphIterables ??= new DefaultGraphIterables<TV, TE>(this);
}